package org.clockwise.multimethod;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.clockwise.srw.UserRelationPool;
import org.clockwise.util.MyFileWriter;
import org.clockwise.util.MyMath;

import Jama.Matrix;

public class SRW {

	public static double epsilon = 0.001;
	private static int plotNum = 50;
//	public static double rate = 0.2;
	public static double penalty = 0.2;

	public static boolean converge(double[] v) {
		double sum = 0.0;
		for (int i = 0; i < v.length; i++) {
			sum += Math.abs(v[i]);
		}
		return sum < epsilon;
	}


	public static double norm1(double[] a, double[] b) {
		if (a.length != b.length) {
			System.out
					.println("Fatal Error! The two vectors are of different dimension");
			return Double.MAX_VALUE;
		}
		double delta = 0.0;
		for (int i = 0; i < a.length; i++) {
			delta += Math.abs(a[i] - b[i]);
		}
		return delta;
	}

	public static double[] trainWithoutHer(List<List<List<double[]>>> data,
			List<List<List<Long>>> dict, List<double[][]> ucFeature,
			List<double[][][]> uuFeature, List<double[][]> relation, List<int[]> ruler, List<Long[]> idList) {
		double[] bestWeight = null;
		double bestAlpha = 0;
		double bestRate = 0;
		double bestAcc = 0;
		int T = 150;
		MyFileWriter mfw = new MyFileWriter("SRWresult.txt", false);
		
		assert (data != null && dict != null);
		assert (ucFeature != null && uuFeature != null && relation != null && ruler != null);
		
		double alpha = 0.1;
		double beta = 100;
		double lamda = 1;
		double eta = 10;
		double oRate = 4E-12;
		double tRate = 0.0003;
		
//		bestWeight = trainSingleDay(relation, ruler, ucFeature, uuFeature, alpha, beta, lamda, eta, oRate, tRate, T);
		for (alpha = 0.6; alpha < 0.7; alpha += 0.5) {
			System.out.print("Condition: alpha = " + alpha + "\n");

			double[] w = trainSingleDay(relation, ruler, ucFeature, uuFeature, alpha, beta, lamda, eta, oRate, tRate, T);
			
			double[] omega = Arrays.copyOfRange(w, 0, uuFeature.get(0)[0][0].length);
			double[] theta = Arrays.copyOfRange(w, omega.length, w.length);
			
			UserRelationPool.updateRelationMatrix(uuFeature, ruler, omega, relation, eta);
			
			double correct = PageRank.test(relation, ucFeature, ruler, theta, alpha);

			System.out.println(" precision = " + correct);

			if (correct > bestAcc) {
				bestAcc = correct;
				bestAlpha = alpha;
				bestWeight = Arrays.copyOf(w, w.length);
			}
			StringBuilder sb = new StringBuilder();
			sb.append(correct);
			sb.append("\t");
			sb.append(alpha);
			sb.append("\t");
			for (int i = 0; i < w.length; i++) {
				sb.append("\t");
				sb.append(w[i]);
			}
			sb.append("\n");
			mfw.write(sb.toString());
//			System.out.println(sb.toString());
		}
		
		
		return bestWeight;
	}
	
	public static double[] trainSingleDay(List<double[][]> relation,
			List<int[]> ruler, List<double[][]> ucFeature,
			List<double[][][]> uuFeature, double alpha, double beta, double lamda,
			double eta, double oRate, double tRate, int T) {
		
		assert (ruler.size() == ucFeature.size());
		assert (uuFeature.size() == ucFeature.size());
		
		assert (uuFeature.get(0) != null);
		assert (uuFeature.get(0)[0] != null);
		assert (uuFeature.get(0)[0][0] != null);
		
		int odim = uuFeature.get(0)[0][0].length;
		int tdim = ucFeature.get(0)[0].length;
		double[] omega = new double[odim];
		double[] theta = new double[tdim];
		Arrays.fill(omega, 1.0);
		Arrays.fill(theta, 1.0);
		
		int t = 0;
		double[] prevO = new double[odim];
		double[] prevT = new double[tdim];
		while (t < T && (!MyMath.almostEqual(omega, prevO) || !MyMath.almostEqual(theta, prevT))) {
			t++;
			UserRelationPool.updateRelationMatrix(uuFeature, ruler, omega, relation, eta);
			double[] deltaOmega = new double[odim];
			double[] deltaTheta = new double[tdim];
			Arrays.fill(deltaOmega, 0.0);
			Arrays.fill(deltaTheta, 0.0);
			Iterator<double[][][]> uufIter = uuFeature.iterator();
			Iterator<double[][]> fwIter = relation.iterator();
			Iterator<double[][]> ucfIter = ucFeature.iterator();
			Iterator<int[]> sIter = ruler.iterator();
			
			while (uufIter.hasNext() && fwIter.hasNext() && 
					ucfIter.hasNext() && sIter.hasNext()) {
				double[][][] uuf = uufIter.next();
				double[][] fw = fwIter.next();
				double[][] ucf = ucfIter.next();
				int[] start = sIter.next();
				
				double[] p = PageRank.predict(fw, start, ucf, theta, alpha);
				
				int n = fw.length;
				double[][] A = new double[n][n];
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < n; j++) {
						A[i][j] = (-alpha) * fw[i][j];
					}
				}
				for (int i = 0; i < n; i++) {
					A[i][i] += 1;
				}
				
				A = SRW.inv(A);		
				
				double[] dOmega = UserRelationPool.deltaOmega(uuf, fw, A, ucf, start, p, omega, alpha, beta, lamda, eta);
				for (int i = 0; i < odim; i++) {
					deltaOmega[i] += dOmega[i];
				}
				double[] dTheta = SRW.deltaTheta(fw, A, ucf, start, theta, p, alpha, lamda, beta);
				for (int i = 0; i < tdim; i++) {
					deltaTheta[i] += dTheta[i];
				}
			}
			
			for (int i = 0; i < odim; i++) {
				omega[i] -= oRate * deltaOmega[i];
				if (omega[i] < 0) {
//					omega[i] = 0;
				}
			}
			for (int i = 0; i < tdim; i++) {
				theta[i] -= tRate * deltaTheta[i];
				if (theta[i] < 0) {
//					theta[i] = 0;
				}
			}
//			System.out.println(omega[0] + "\t" + theta[0]);
			
			double[] weight = new double[odim + tdim];
			for (int i = 0; i < odim; i++) {
				weight[i] = omega[i];
			}
			for (int i = 0; i < tdim; i++) {
				weight[odim + i] = theta[i];
			}
			prevO = Arrays.copyOf(deltaOmega, deltaOmega.length);
			prevT = Arrays.copyOf(deltaTheta, deltaTheta.length);
			MyMath.printVector(weight);
		}
		
		if (t == T) {
//			System.out.println("May not converge when alpha = " + alpha);
		}
		
		double[] weight = new double[odim + tdim];
		for (int i = 0; i < odim; i++) {
			weight[i] = omega[i];
		}
		for (int i = 0; i < tdim; i++) {
			weight[odim + i] = theta[i];
		}
		
		return weight;
	}
	
	public static double[] trainWithNeuralModel(
			List<List<List<double[]>>> data, List<double[][]> relation,
			List<double[][]> feature, List<int[]> ruler) {
		double[] bestW = null;
		double bestAlpha = 0;
		double bestRate = 0;
		double bestAcc = 0;
		int T = 80;
		MyFileWriter mfw = new MyFileWriter("NMresult.txt", false);
		for (double alpha = 0.0; alpha < 0.1; alpha += 0.05) {
			for (double rate = 0.0002; rate <= 0.0002; rate += 0.0001) {

				System.out.print("Condition: alpha = " + alpha + " rate = "
						+ rate + "\n");

				double[] w = unitTrain(relation, ruler, feature, alpha, rate, T);

				double correct = PageRank.test(relation, feature, ruler, w,
						alpha);

				System.out.println(" precision = " + correct);

				if (correct > bestAcc) {
					bestAcc = correct;
					bestAlpha = alpha;
					bestW = Arrays.copyOf(w, w.length);
				}
				StringBuilder sb = new StringBuilder();
				sb.append(correct);
				sb.append("\t");
				sb.append(alpha);
				sb.append("\t");
				sb.append(rate);
				for (int i = 0; i < w.length; i++) {
					sb.append("\t");
					sb.append(w[i]);
				}
				sb.append("\n");
				// mfw.write(sb.toString());
				System.out.println(sb.toString());
			}
		}
		mfw.close();
		return bestW;
	}

	private static double[] unitTrain(List<double[][]> relation,
			List<int[]> ruler, List<double[][]> feature, double alpha,
			double rate, int T) {

		double[] w = new double[feature.get(0)[0].length];
		Arrays.fill(w, 1.0);

		for (int t = 0; t < T; t++) {
			// System.out.println("\tRound " + (t + 1));
			Iterator<double[][]> mIter = relation.iterator();
			Iterator<double[][]> fIter = feature.iterator();
			Iterator<int[]> sIter = ruler.iterator();
//			double r = rate / (t + 1);
			 double r = rate;
			double[] deltaW = new double[w.length];
			Arrays.fill(deltaW, 0.0);
			while (mIter.hasNext() && fIter.hasNext() && sIter.hasNext()) {
				double[][] matrix = mIter.next();
				double[][] f = fIter.next();
				int[] start = sIter.next();
				double[] p = PageRank.predict(matrix, start, f, w, alpha);
				// System.out.println("First Predict");

				 assert (PageRank.checkVectorNormalized(p));
				if (!PageRank.checkVectorNormalized(p)) {
					MyMath.printVector(p);
				}
				// step0: calcluate inv(I - alpha * P')
				
				int n = matrix.length;
				double[][] A = new double[n][n];
				for (int i = 0; i < n; i++) {
					for (int j = 0; j < n; j++) {
						A[i][j] = (-alpha) * matrix[i][j];
					}
				}
				for (int i = 0; i < n; i++) {
					A[i][i] += 1;
				}
				
				A = SRW.inv(A);		
				double[] dw = SRW.deltaTheta(matrix, A, f, start, w, p, alpha, 1, 100);
//				MyMath.printVector(dw);
				for (int j = 0; j < w.length; j++) {
					deltaW[j] += dw[j];
				}
			}
//			MyMath.printVector(deltaW);
//			assert (deltaW[0] != Double.NaN);
//			System.out.print("Before update: ");
//			MyMath.printVector(w);
			for (int i = 0; i < w.length; i++) {
				w[i] -= r * deltaW[i];
				if (w[i] < 0) {
//					System.out.println("Warning! Negative element in weight vector!");
					w[i] = 0;
				}
			}

			MyMath.printVector(deltaW);
		}
		return w;
	}

	private static int deltaCounter = 0;
	
	/*
	 * A = inv(I - alpha * P')
	 * P' = m
	 */
	private static double[] deltaTheta(double[][] m, double[][] A, double[][] f, int[] start,
			double[] theta, double[] p, double alpha, double lamda, double beta) {
		deltaCounter++;
		// double[] deltaTheta = Arrays.copyOf(theta, theta.length);
		assert (m.length == f.length && m.length == p.length);
		assert (lamda >= 0 && beta >= 0);
		assert (f[0].length == theta.length);
		assert (PageRank.checkVectorNormalized(p));
		int dim = theta.length;
		int n = m.length;
		
		// step0: calcluate inv(I - alpha * P')
		/*
		double[][] A = new double[n][n];
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				A[i][j] = (-alpha) * m[i][j];
			}
		}
		for (int i = 0; i < n; i++) {
			A[i][i] += 1;
		}
		*/
//		MyMath.printMatrix(m);
/*		A = SRW.inv(A);		*/
//		MyMath.printMatrix(A);
//		System.exit(1);
		
		// step1: calculate partial_v_k / partial_theta_i

		// substep1: calculate non-normalized scores

		/*
		double[] v = new double[n];
		Arrays.fill(v, 0.0);
		for (int k = 0; k < n; k++) {
			for (int i = 0; i < dim; i++) {
				v[k] += f[k][i] * theta[i];
			}
		}
		*/
		double[] v = PageRank.getFeatureVector(f, theta);

		// substep2: calculate sum of scores and its squared reciprocal
		double sumftheta = 0.0;
		for (int k = 0; k < n; k++) {
			sumftheta += v[k];
		}
		assert (sumftheta != 0.0);
		double sr_sumftheta = 1.0 / sumftheta / sumftheta;

		// substep3: calculate the sum of feature of each dimension
		double[] sumf = new double[dim];
		Arrays.fill(sumf, 0.0);
		for (int i = 0; i < dim; i++) {
			for (int k = 0; k < n; k++) {
//				sumf[i] += f[k][i];			// used for linear scoring only
				sumf[i] += f[k][i] * v[k];	// used for exponential scoring only
			}
			sumf[i] *= PageRank.gama;		// used for exponential scoring only
		}

		// substep4: calculate partial_v_k / partial_theta_i
		double[][] partial_v_t = new double[n][dim];
		for (int k = 0; k < n; k++) {
			Arrays.fill(partial_v_t[k], 0.0);
		}
		for (int k = 0; k < n; k++) {
			for (int i = 0; i < dim; i++) {
				partial_v_t[k][i] = (f[k][i] * sumftheta - v[k] * sumf[i]) * sr_sumftheta;	// used for linear scoring
				partial_v_t[k][i] *= PageRank.gama * v[k];
			}
		}

		// System.out.println("Partial_v_k");
		// MyMath.printMatrix(partial_v_t);

		// step2: calculate partial_p_j / partial_theta_i
		double[][] partial_p_t = new double[n][dim];
		for (int j = 0; j < n; j++) {
			Arrays.fill(partial_p_t[j], 0.0);
		}
		for (int k = 0; k < n; k++) {
			for (int i = 0; i < dim; i++) {
				for (int j = 0; j < n; j++) {
					partial_p_t[j][i] += partial_v_t[k][i] * A[k][j];
				}
			}
		}

		double c_alpha = 1.0 - alpha;

		for (int j = 0; j < n; j++) {
			for (int i = 0; i < dim; i++) {
				partial_p_t[j][i] *= c_alpha;
			}
		}

		// System.out.println("Partial_p_t = ");
		// MyMath.printMatrix(partial_p_t);

		// step3: calculate delta_theta
		double[] deltaTheta = new double[dim];
		Arrays.fill(deltaTheta, 0.0);
		for (int k = 0; k < n; k++) {
			// This is an easy version, not strictly equivalent with the
			// formulation.
			for (int j = 0; j < start.length - 1; j++) {
				for (int i = 0; i < dim; i++) {
					deltaTheta[i] += partial_sigmoid(p[k] - p[start[j]], beta)
							* (partial_p_t[k][i] - partial_p_t[start[j]][i]);
					Double temp = deltaTheta[i];
					assert (!temp.isNaN());
				}
			}
		}
//		MyMath.printVector(deltaTheta);
		
		for (int i = 0; i < dim; i++) {
			deltaTheta[i] *= lamda;
			deltaTheta[i] += 2 * theta[i];
		}
		// System.out.println("deltaTheta = ");
		// MyMath.printVector(deltaTheta);
		
		return deltaTheta;
	}

	public static double sigmoid(double x, double beta) {
		return 1.0 / (1.0 + Math.exp(-beta * x));
	}

	public static double partial_sigmoid(double delta, double beta) {
		Double temp = Math.exp(-beta * delta);
		assert (!temp.isInfinite() && !temp.isNaN() && temp != 0.0);
		return beta * temp / ((1.0 + temp) * (1.0 + temp));
	}
	
	public static double[][] inv(double[][] matrix) {
		assert (matrix != null && matrix.length == matrix[0].length);
		
		int dim = matrix.length;
		Matrix m = new Matrix(matrix, dim, dim);
		Matrix invm = m.inverse();
		return invm.getArray();
	}
	
}